package ar.edu.unlu.sistemas.movirdroid.business.index;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import android.content.Context;
import android.util.Log;
import ar.edu.unlu.sistemas.movirdroid.android.AppService;
import ar.edu.unlu.sistemas.movirdroid.business.util.Constants;
import ar.edu.unlu.sistemas.movirdroid.business.util.Logger;
import ar.edu.unlu.sistemas.movirdroid.business.util.StringUtils;
import ar.edu.unlu.sistemas.movirdroid.business.vo.DocumentVO;
import ar.edu.unlu.sistemas.movirdroid.business.vo.TermFrecuencyVO;
import ar.edu.unlu.sistemas.movirdroid.business.vo.WordVO;
import ar.edu.unlu.sistemas.ws.business.ResourceDescriptionWS;

public class IndexController {
	
	private static IndexController INSTANCE;
	private int wordSequence;
	private int docSequence;
	private HashMap<String,WordVO> words;
	private HashMap<String,DocumentVO> documents;
	private HashMap<String,Collection<TermFrecuencyVO>> tfsByWord;
	private HashMap<String,Collection<TermFrecuencyVO>> tfsByDoc;

	public ResourceDescriptionWS getResourceDescription() {
		ResourceDescriptionWS index = new ResourceDescriptionWS();
		Map<String,Integer> indexMap =new HashMap<String,Integer>();
		Integer ac = new Integer(0);
		for (String word : this.tfsByWord.keySet()) {
			ac += this.tfsByWord.get(word).size();
			indexMap.put(word, ac);
		}
		index.setResourceDescription(indexMap);
		index.setnDocs(this.documents.size());
		return index;
	}
	
	private int getWordSequence() {
		return wordSequence;
	}

	private void setWordSequence(int wordSequence) {
		this.wordSequence = wordSequence;
	}

	private void incWordSequence() {
		this.setWordSequence(this.wordSequence +1);
	}
	
	private int getDocSequence() {
		return docSequence;
	}

	private void setDocSequence(int docSequence) {
		this.docSequence = docSequence;
	}
	
	private void incDocumentSequence() {
		this.setDocSequence(this.docSequence +1);
	}

	private IndexController() {
		try {
			 FileInputStream fis = AppService.instance.openFileInput("words");
			 ObjectInputStream ois = new ObjectInputStream(fis);
			 this.words = (HashMap<String,WordVO>) ois.readObject();
			 ois.close();
			 fis = AppService.instance.openFileInput("docs");
			 ois = new ObjectInputStream(fis);
			 this.documents = (HashMap<String,DocumentVO>) ois.readObject();
			 ois.close();
			 fis = AppService.instance.openFileInput("tfwords");
			 ois = new ObjectInputStream(fis);
			 this.tfsByWord = (HashMap<String,Collection<TermFrecuencyVO>>) ois.readObject();
			 ois.close();
			 fis = AppService.instance.openFileInput("tfdocs");
			 ois = new ObjectInputStream(fis);
			 this.tfsByDoc = (HashMap<String,Collection<TermFrecuencyVO>>) ois.readObject();
			 ois.close();
		} catch (Exception e) {
			Logger.logError("Index", e.getMessage(), e);
			this.words = new HashMap<String,WordVO>();
			this.documents = new HashMap<String,DocumentVO>();
			this.tfsByWord = new HashMap<String,Collection<TermFrecuencyVO>>();
			this.tfsByDoc = new HashMap<String,Collection<TermFrecuencyVO>>();
		}
		this.initSequences();
		
		
	}
	
	public static IndexController getInstance() {
		if (INSTANCE == null) {
			INSTANCE = new IndexController();
		}
		return INSTANCE;
	}
	
	public void saveIndex() {
		try {
			FileOutputStream fos = AppService.instance.openFileOutput("words", Context.MODE_PRIVATE);
		    ObjectOutputStream oos = new ObjectOutputStream(fos);
		    oos.writeObject(this.words);
		    oos.close();
		    fos = AppService.instance.openFileOutput("docs", Context.MODE_PRIVATE);
		    oos = new ObjectOutputStream(fos);
		    oos.writeObject(this.documents);
		    oos.close();
		    fos = AppService.instance.openFileOutput("tfwords", Context.MODE_PRIVATE);
		    oos = new ObjectOutputStream(fos);
		    oos.writeObject(this.tfsByWord);
		    oos.close();
		    fos = AppService.instance.openFileOutput("tfdocs", Context.MODE_PRIVATE);
		    oos = new ObjectOutputStream(fos);
		    oos.writeObject(this.tfsByDoc);
		    oos.close();
		} catch (Exception e) {
			
		}
	}

	/**
	 * Inicia la secuencias de las tablas de Termino y Documento ya que no sabemos si
	 * el motor de BD que utilizamos soporte secuencias. Por eso mismo lo manejamos
	 * por código.
	 */
	public void initSequences() {
		Log.i(this.getClass().getSimpleName(), "Inicio las secuencias de BD");
		this.setDocSequence(this.documents.size());
		this.setWordSequence(this.words.size());
	}
	
	//-------------------------------------------------------------------------------------------------------
	//Entidad TERMINO
	//-------------------------------------------------------------------------------------------------------
	

	public synchronized void insertWord(String word) {
		Log.i(this.getClass().getSimpleName(), "Se inserta el el índice la palabra "+word);
		new WordVO(this.getWordSequence(),word);
		this.words.put(word,new WordVO(this.getWordSequence(),word));
		this.incWordSequence();
	}
	
	/**
	 * Retorna el value object de un TERMINO identificado con la clave indicada como parámetro
	 * 
	 * @param word
	 * @return value object del TERMINO
	 */
	public synchronized WordVO findWordByName(String word) {
		return this.words.get(word);
	}
	

	
	/**
	 * Retorna todos los TERMINOS existentes en la tabla de la entidad
	 * 
	 * @return value objects de todas las filas de la tabla TERMINO
	 */
	public synchronized Collection<WordVO> findAllWords() {
		Log.i(this.getClass().getSimpleName(), "Busco todas las palabras");
		return this.words.values();
	}
	
	
	/**
	 * Retorna todos los terminos los cuales su nombre este dentro del subconjunto
	 * que se indica como parámetro
	 * 
	 * @param words
	 * @return colección de value objects de entidad TERMINO
	 */
	public synchronized Collection<WordVO> findWordsByNames(Collection<String> words) {
		Log.i(this.getClass().getSimpleName(), "Busco palabras dentro del conjunto "+StringUtils.convertToSQLSet(words));
		Collection<WordVO> result = new ArrayList<WordVO>();
		String wordi;
		WordVO wordiVo;
		for (Iterator<String> it = words.iterator(); it.hasNext(); ) {
			wordi = it.next();
			wordiVo = this.words.get(wordi);
			if (wordiVo != null) {
				result.add(wordiVo);
			}
		}
		return result;
	}
	
	/**
	 * Retorna todos los terminos que su clave se parece a la indicada
	 * 
	 * @param word
	 * @return colección de value objects de entidad TERMINO
	 */
	public synchronized Collection<WordVO> findWordsLikeName(String word) {
		Log.i(this.getClass().getSimpleName(), "Busco palabras como "+word);
		Collection<WordVO> result = new ArrayList<WordVO>();
		String wordi;
		for (Iterator<String> it = this.words.keySet().iterator(); it.hasNext(); ) {
			wordi = it.next();
			if (wordi.contains(word)) result.add(this.words.get(wordi));
		}
		return result;
	}
	
	public synchronized Collection<WordVO> findWordsByNames(String[] words) {
		Log.i(this.getClass().getSimpleName(), "Busco documentos dentro del conjunto "+StringUtils.convertToSQLSet(words));
		Collection<WordVO> result = new ArrayList<WordVO>();
		WordVO iwordVO;
		for (int i=0;i<words.length;i++) {
			iwordVO = this.words.get(words[i]);
			if (iwordVO != null) {
				result.add(iwordVO);
			}
		}
		return result;
	}
	
	public synchronized WordVO findWordById(int id) {
		WordVO itVO;
		WordVO result = null;
		for (Iterator<WordVO> it = this.words.values().iterator();it.hasNext();) {
			itVO = it.next();
			if (itVO.getId() == id) {
				result = itVO;
				break;
			}
		}
		return result;
	}
	
	public synchronized void deleteWord(String id) {
		AppService.instance.deleteFile("words");
		this.words.clear();
	}
	
	//-------------------------------------------------------------------------------------------------------
	//Entidad DOCUMENTO
	//-------------------------------------------------------------------------------------------------------
	
	/**
	 * Inserta un DOCUMENTO con la clave pasada por parámetro
	 */
	public synchronized int insertDoc(String doc) {
		Log.i(this.getClass().getSimpleName(), "Se inserta el el índice el documento "+doc);
		DocumentVO docVO = new DocumentVO();
		docVO.setName(doc);
		docVO.setId(this.getDocSequence());
		this.documents.put(doc, docVO);
		this.incDocumentSequence();
		return docVO.getId();
	}
	
	
	/**
	 * Método para obtener un DOCUMENTO a partir de su nombre clave
	 * 
	 * @param doc
	 * @return value object del DOCUMENTO obtenido
	 */
	public synchronized DocumentVO findDocumentByName(String doc) {
		Log.i(this.getClass().getSimpleName(), "Se busca el documento por nombre "+doc);
		return this.documents.get(doc);
	}
	
	
	/**
	 * Retorna nombre de todos los documentos
	 * 
	 * @return colección de value objects de DOCUMENTO
	 */
	public synchronized Collection<String> findAllDocsNames() {
		Log.i(this.getClass().getSimpleName(), "Busco todos los documentos");
		return this.documents.keySet();
	}
	
	/**
	 * Retorna todos los documentos existentes en la tabla DOCUMENTO
	 * 
	 * @return colección de value objects de DOCUMENTO
	 */
	public synchronized Collection<DocumentVO> findAllDocs() {
		Log.i(this.getClass().getSimpleName(), "Busco todos los documentos");
		return this.documents.values();
	}
	
	/**
	 * Retorna todos los documentos los cuales su clave este contenida
	 * en el subconjunto indicado como parámetro
	 * 
	 * @param docs
	 * @return colección de valueObjects de DOCUMENTO
	 */
	public synchronized Collection<DocumentVO> findDocsByName(Collection<String> docs) {
		Log.i(this.getClass().getSimpleName(), "Busco documentos dentro del conjunto "+StringUtils.convertToSQLSet(docs));
		Collection<DocumentVO> result = new ArrayList<DocumentVO>();
		String idoc;
		DocumentVO idocVo;
		for (Iterator<String> it = docs.iterator(); it.hasNext(); ) {
			idoc = it.next();
			idocVo = this.documents.get(idoc);
			if (idocVo != null) {
				result.add(idocVo);
			}
		}
		return result;
	}
	
	/**
	 * Retorna todos los documentos los cuales su clave este contenida
	 * en el subconjunto indicado como parámetro
	 * 
	 * @param docs
	 * @return colección de valueObjects de DOCUMENTO
	 */
	public synchronized Collection<DocumentVO> findDocsByName(String[] docs) {
		Log.i(this.getClass().getSimpleName(), "Busco documentos dentro del conjunto "+StringUtils.convertToSQLSet(docs));
		Collection<DocumentVO> result = new ArrayList<DocumentVO>();
		DocumentVO idocVo;
		for (int i=0;i<docs.length;i++) {
			idocVo = this.documents.get(docs[i]);
			if (idocVo != null) {
				result.add(idocVo);
			}
		}
		return result;
	}
	
	/**
	 * Retorna todos los documentos en los cuales la clave contiene
	 * la cadena de caracteres enviada como parámetro
	 * 
	 * @param doc
	 * @return colección de valueObjects de DOCUMENTO
	 */
	public synchronized Collection<DocumentVO> findDocsLikeName(String doc) {
		Log.i(this.getClass().getSimpleName(), "Busco documentos como "+doc);
		Collection<DocumentVO> result = new ArrayList<DocumentVO>();
		String idoc;
		for (Iterator<String> it = this.documents.keySet().iterator(); it.hasNext(); ) {
			idoc = it.next();
			if (idoc.contains(doc)) {
				result.add(this.documents.get(idoc));
			}
		}
		return result;
	}
	
	/**
	 * Remueve un documento identificado por su clave borrando también los
	 * registros que apuntan al mismo en la tabla TERMINO-FRECUENCIA
	 * 
	 * @param doc
	 */
	public synchronized Collection<TermFrecuencyVO> removeDoc(String doc) {
		Log.i(this.getClass().getSimpleName(), "Remuevo documento "+doc);
		this.documents.remove(doc);
		return this.tfsByDoc.remove(doc);
	}
	
	
	public synchronized void deleteDoc(String id) {
		AppService.instance.deleteFile("docs");
		this.documents.clear();
	}
	
	/**
	 * Por cada documento que contiene algunos de los términos pasados por parámetro, retorno una entrada a un Map
	 * con dichos terminos y sus frecuencias
	 * @param words
	 * @return [ Documento : [ Termino : Frecuencia ] ]
	 */
	public synchronized Map<DocumentVO,Map<String,TermFrecuencyVO>> findDocsByWords(String[] words) {
		Map<DocumentVO,Map<String,TermFrecuencyVO>> map = new HashMap<DocumentVO,Map<String,TermFrecuencyVO>>();
		Iterator<TermFrecuencyVO> tfs;
		for (int i = 0; i<words.length;i++) {
			//EVITO LA SUMA DE CUADRADOS PORQUE EXISTE EN TODOS LOS DOCUMENTOS
			if (!Constants.SQUARE_SUM_WORD.equals(words[i])) {
				tfs = this.findTFsByWord(words[i]).iterator();
				for (TermFrecuencyVO tf = tfs.next();tfs.hasNext();) {
					if (!map.containsKey(tf.getDocument())) {
						map.put(tf.getDocument(), new HashMap<String,TermFrecuencyVO>());
						//COLOCO LA SUMA DE CUADRADOS
						map.get(tf.getDocument()).put(Constants.SQUARE_SUM_WORD, this.findTFsByDocumentAndWords(tf.getDocument().getName(), Constants.SQUARE_SUM_WORD));
					}
					map.get(tf.getDocument()).put(words[i], tf);
				}
			}
			
		}
		return map;
	}
	
	//-------------------------------------------------------------------------------------------------------
	//Entidad TERMINO-FRECUENCIA
	//-------------------------------------------------------------------------------------------------------
	
	
	/**
	 * Inserta una fila de la entidad TERMINO-FRECUENCIA con las claves
	 * del TERMINO y DOCUMENTO en vez de su id
	 */
	public synchronized void insertTf(String doc,String word, float tf) {
		Log.i(this.getClass().getSimpleName(), "Se inserta el el índice tf "+String.valueOf(tf)+" de palabra "+word+" para el documento "+doc);
		TermFrecuencyVO tfVO = new TermFrecuencyVO();
		tfVO.setDocument(this.documents.get(doc));
		tfVO.setWord(this.words.get(word));
		tfVO.setValue(tf);
		if (this.tfsByWord.get(word) == null) {
			this.tfsByWord.put(word, new ArrayList<TermFrecuencyVO>());
		}
		this.tfsByWord.get(word).add(tfVO);
		if (this.tfsByDoc.get(doc) == null) {
			this.tfsByDoc.put(doc, new ArrayList<TermFrecuencyVO>());
		}
		this.tfsByDoc.get(doc).add(tfVO);
	}
	
	
	/**
	 * Retorna las entidades TERMINO-FRECUENCIA relacionadas con el DOCUMENTO
	 * identificado por la clave pasada por parámetro
	 * 
	 * @param doc
	 * @return colección value-objects TERMINO-FRECUENCIA
	 */
	public synchronized Collection<TermFrecuencyVO> findTFsByDocument(String doc) {
		Log.i(this.getClass().getSimpleName(), "Se buscan frecuencias por documento "+doc);
		return this.tfsByDoc.get(doc);
	}
	
	/**
	 * Retorna las entidades TERMINO-FRECUENCIA relacionadas con el DOCUMENTO
	 * identificado por la clave pasada por parámetro
	 * 
	 * @param doc
	 * @return colección value-objects TERMINO-FRECUENCIA
	 */
	public synchronized Collection<TermFrecuencyVO> findTFsByDocumentAndWords(String doc, String[] words) {
		Log.i(this.getClass().getSimpleName(), "Se buscan frecuencias por documento "+doc);
		Collection<TermFrecuencyVO> result = new ArrayList<TermFrecuencyVO>();
		if (this.tfsByDoc.get(doc) != null ) {
			Collection<WordVO> tempColl = this.findWordsByNames(words);
			Collection<TermFrecuencyVO> coll = this.tfsByDoc.get(doc);
			TermFrecuencyVO itfVO = new TermFrecuencyVO();
			for (Iterator<TermFrecuencyVO> it = coll.iterator(); it.hasNext();) {
				itfVO = it.next();
				if (tempColl.contains(itfVO)) result.add(itfVO);
			}
		}
		return result;
	}
	
	/**
	 * Retorna la intersección en la "matriz" término-documento
	 * @param doc
	 * @param word
	 * @return
	 */
	public synchronized TermFrecuencyVO findTFsByDocumentAndWords(String doc, String word) {
		Log.i(this.getClass().getSimpleName(), "Se buscan frecuencia de "+word+" en documento "+doc);
		if (this.tfsByDoc.get(doc) != null ) {
			WordVO wordVO = this.findWordByName(word);
			Collection<TermFrecuencyVO> coll = this.tfsByDoc.get(doc);
			TermFrecuencyVO itfVO = new TermFrecuencyVO();
			for (Iterator<TermFrecuencyVO> it = coll.iterator(); it.hasNext();) {
				itfVO = it.next();
				if (wordVO.equals(itfVO)) return itfVO;
			}
		}
		return null;
	}
	

	/**
	 * Retorna las entidades TERMINO-FRECUENCIA relacionadas con el TERMINO
	 * identificado por la clave pasada por parámetro
	 * 
	 * @param word
	 * @return colección value-objects TERMINO-FRECUENCIA
	 */
	public synchronized Collection<TermFrecuencyVO> findTFsByWord(String word) {
		Log.i(this.getClass().getSimpleName(), "Se buscan frecuencias por termino "+word);
		return this.tfsByWord.get(word);
	}
	
	
	public synchronized void deleteTf(String id) {
		AppService.instance.deleteFile("tfwords");
		AppService.instance.deleteFile("tfdocs");
		this.tfsByDoc.clear();
		this.tfsByWord.clear();
	}
	
}
 